
// ===============================================================================================================
// -*- C++ -*-
//
// ShaderProgram.cpp - GPU shader program interface.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <ShaderProgram.hpp>

ShaderProgram * ShaderProgram::Create(const std::string * srcFiles, size_t numFiles)
{
	try {

		if (!srcFiles || (numFiles == 0))
		{
			return (0);
		}

		return (new ShaderProgram(srcFiles, numFiles));
	}
	catch (...) {

		return (0); // Returns null on failure.
	}
}

void ShaderProgram::Enable(void) const
{
	glUseProgram(programObj);
}

void ShaderProgram::Disable(void) const
{
	glUseProgram(0); // Switch back to fixed functionality
}

void ShaderProgram::SetUniform1i(const char * name, int i)
{
	GLint uniLoc = glGetUniformLocation(programObj, name);
	glUniform1i(uniLoc, i);
}

void ShaderProgram::SetUniform1f(const char * name, float f)
{
	GLint uniLoc = glGetUniformLocation(programObj, name);
	glUniform1f(uniLoc, f);
}

void ShaderProgram::SetUniform4f(const char * name, float vec[4])
{
	GLint uniLoc = glGetUniformLocation(programObj, name);
	glUniform4fv(uniLoc, 1, vec);
}

ShaderProgram::~ShaderProgram(void)
{
	if (!shaders.empty())
	{
		std::vector<GLuint>::const_iterator it = shaders.begin();
		std::vector<GLuint>::const_iterator end = shaders.end();

		while (it != end)
		{
			glDetachShader(programObj, *it);
			glDeleteShader(*it);
			++it;
		}

		shaders.clear();
	}

	glDeleteProgram(programObj);
}

ShaderProgram::ShaderProgram(const std::string * srcFiles, size_t numFiles)
{
	programObj = glCreateProgram();

	for (size_t i = 0; i < numFiles; ++i)
	{
		GLenum shaderType;
		GLuint shader;
		char * buffer;

		std::string ext; // Extract file extension:
		ext.assign(srcFiles[i], srcFiles[i].find_last_of('.') + 1, std::string::npos);

		// Case-insensitive compare:
		if (stricmp(ext.c_str(), "vert") == 0)
		{
			shaderType = GL_VERTEX_SHADER;
		}
		else if (stricmp(ext.c_str(), "frag") == 0)
		{
			shaderType = GL_FRAGMENT_SHADER;
		}
		else
		{
			LOG_ERROR("Invalid shader file extension! Must be either .vert of .frag");
			continue;
		}

		FILE * file = fopen(srcFiles[i].c_str(), "rb");

		if (file != 0)
		{
			fseek(file, 0, SEEK_END);
			long fileSize = ftell(file);
			fseek(file, 0, SEEK_SET);

			buffer = new char [fileSize + 1];

			fread(buffer, sizeof(char), fileSize, file);
			fclose(file);

			buffer[fileSize] = '\0';

			shader = glCreateShader(shaderType);
			glShaderSource(shader, 1, const_cast<const char **>(&buffer), 0);

			delete[] buffer;

			glCompileShader(shader);
			glAttachShader(programObj, shader);

			shaders.push_back(shader);
		}
	}

	glLinkProgram(programObj);

	WriteInfoLog();
}

void ShaderProgram::WriteInfoLog(void) const
{
	try {

		GLsizei infoLogLen;
		GLsizei charsWritten;
		std::string infoLogStr;

		std::vector<GLuint>::const_iterator it = shaders.begin();
		std::vector<GLuint>::const_iterator end = shaders.end();

		while (it != end)
		{
			infoLogLen = 0;
			charsWritten = 0;
			char * shaderLogPtr;

			glGetShaderiv(*it, GL_INFO_LOG_LENGTH, &infoLogLen);

			if (infoLogLen > 1)
			{
				shaderLogPtr = new char [infoLogLen + 1];
				glGetShaderInfoLog(*it, infoLogLen, &charsWritten, shaderLogPtr);
				shaderLogPtr[infoLogLen] = '\0';
				infoLogStr.append(shaderLogPtr);
				delete[] shaderLogPtr;
			}

			++it;
		}

		infoLogLen = 0;
		charsWritten = 0;
		char * programLogPtr;

		glGetProgramiv(programObj, GL_INFO_LOG_LENGTH, &infoLogLen);

		if (infoLogLen > 1)
		{
			programLogPtr = new char [infoLogLen + 1];
			glGetProgramInfoLog(programObj, infoLogLen, &charsWritten, programLogPtr);
			programLogPtr[infoLogLen] = '\0';
			infoLogStr.append(programLogPtr);
			delete[] programLogPtr;
		}

		if (!infoLogStr.empty())
		{
			// Send to the defaul log:

			Log::Instance() << '\n';
			Log::Instance() << "SHADER PROGRAM INFO LOG:";
			Log::Instance() << '\n';
			Log::Instance() << infoLogStr.c_str() << '\n';
			Log::Instance() << '\n';
		}
	}
	catch (...) {

		LOG_ERROR("Unable to generate the shader program info log! Unknown error...");
	}
}

unsigned long ShaderProgram::AddRef(void) const
{
	return (++refCount);
}

unsigned long ShaderProgram::Release(void) const
{
	if (--refCount == 0)
	{
		// Time to die...
		delete this;
		return (0);
	}

	return (refCount);
}

unsigned long ShaderProgram::ReferenceCount(void) const
{
	return (refCount);
}